AWS VPC의 Public Subnet과 Private Subnet

AWS VPC의 Public Subnet과 Private Subnet

한눈에 보기

AWS에서 subnet을 public 또는 private으로 만드는 전용 type이 있는 것은 아니다. Subnet이 연결된 route table에 Internet Gateway를 향한 route가 있으면 public subnet으로 부른다. IPv4 instance가 인터넷과 직접 통신하려면 이 route뿐 아니라 public IPv4 주소와 security group 허용도 필요하다. Private subnet은 IGW로 직접 가는 route가 없으며 outbound 인터넷이 필요하면 NAT Gateway를 사용한다. DB처럼 인터넷 outbound도 필요 없는 resource는 isolated subnet에 두고 AWS 서비스 접근은 VPC endpoint로 좁힐 수 있다.

목차

Subnet 이름보다 Route Table이 먼저다

public-a, private-a라는 tag는 사람을 위한 이름이다. AWS가 packet을 보낼 때 tag를 보고 판단하지 않는다. Subnet에 associate된 route table을 본다.

public-a:
  10.40.0.0/16 → local
  0.0.0.0/0    → igw-example

private-a:
  10.40.0.0/16 → local
  0.0.0.0/0    → nat-example-a

isolated-a:
  10.40.0.0/16 → local

첫 번째 table은 인터넷 목적지의 IPv4 traffic을 IGW로 보내므로 public이다. 두 번째는 NAT로 보내므로 private이다. 세 번째는 VPC 내부 local route만 있어 isolated라고 부를 수 있다.

flowchart LR
    I[Internet]
    G[Internet Gateway]
    P[Public subnet]
    N[NAT Gateway]
    A[Private application subnet]
    D[Isolated database subnet]

    I --- G
    G --- P
    P --> N
    A --> N
    A --- D
정의

AWS 문서에서 public subnet은 Internet Gateway로 가는 route가 있는 subnet이다. Resource가 실제로 공개됐는지는 IP 주소와 보안 제어까지 봐야 한다.

Public Subnet의 IPv4 인터넷 경로

IPv4 EC2가 인터넷과 직접 통신하려면 다음 조건이 모두 맞아야 한다.

  1. VPC에 Internet Gateway가 attach되어 있다.
  2. Subnet route table이 인터넷 목적지를 IGW로 보낸다.
  3. Instance network interface에 public IPv4 또는 Elastic IP가 있다.
  4. Security group과 Network ACL이 traffic을 허용한다.
  5. Application이 해당 interface와 port에서 listen한다.
sequenceDiagram
    participant C as Internet client
    participant G as Internet Gateway
    participant E as EC2 public IPv4
    participant A as Application

    C->>G: destination public IPv4
    G->>E: translate to private IPv4
    E->>A: security checks and socket
    A-->>C: response through IGW

EC2 OS는 보통 자신의 private IPv4를 인식한다. Internet Gateway가 instance public IPv4와 private IPv4 사이의 논리적인 one-to-one NAT를 수행한다.

Public subnet에는 인터넷에서 직접 받아야 하는 resource만 둔다.

Application instance가 public IP 없이 ALB target으로 private traffic만 받는다면 public subnet에 둘 이유가 적다.

Public Route만 있어도 인터넷이 되는 것은 아니다

다음 subnet에는 IGW default route가 있다.

0.0.0.0/0 → igw-example

그 안의 EC2가 private IPv4만 가지고 있다면 IPv4 인터넷과 직접 통신할 수 없다. Route table은 “어디로 보낼지”를 정하지만 인터넷에서 돌아올 public address를 만들지 않는다.

반대도 마찬가지다. Public IPv4를 가진 instance가 IGW route 없는 subnet에 있으면 그 주소만으로 인터넷에 나갈 수 없다.

IGW route Public IPv4 직접 IPv4 인터넷
있음 있음 보안 규칙 허용 시 가능
있음 없음 불가
없음 있음 불가
없음 없음 불가

map_public_ip_on_launch = true는 subnet에 띄운 instance에 public IP를 자동 할당하는 설정이지 subnet을 public으로 만드는 route 설정이 아니다.

이름 기반 감사의 한계

private-* tag만 검색하지 말고 subnet–route table association과 network interface의 public IP를 함께 검사한다.

Private Subnet의 Outbound를 NAT로 보내기

Private application이 package repository나 외부 API에 IPv4로 접속해야 한다면 public NAT Gateway를 통해 나갈 수 있다.

Private EC2
→ private route table
→ NAT Gateway
→ public subnet route table
→ Internet Gateway
→ external API

Private route table:

Destination    Target
10.40.0.0/16   local
0.0.0.0/0      nat-example-a

NAT가 있는 public subnet route table:

Destination    Target
10.40.0.0/16   local
0.0.0.0/0      igw-example

NAT는 private resource가 시작한 연결의 source address를 변환하고 response를 돌려준다. 인터넷 client가 NAT의 public IP로 임의 연결을 시작해 private EC2에 전달하는 port forwarding 장치는 아니다.

sequenceDiagram
    participant A as Private application
    participant N as NAT Gateway
    participant X as External API

    A->>N: outbound connection
    N->>X: translated source
    X-->>N: response
    N-->>A: reverse translation
    X--xA: unsolicited inbound

Inbound 연결과 Return Traffic 구분하기

“Private subnet은 inbound가 안 된다”는 표현은 너무 넓다. VPC 내부 ALB나 다른 service는 private IP로 application에 연결할 수 있다. NAT를 통해 시작한 outbound connection의 return traffic도 들어온다.

차단되는 것은 인터넷에서 private resource로 직접 시작하는 경로다.

Internet → Private EC2 직접 연결       불가
Internet → Public ALB → Private EC2   가능
Private EC2 → Internet → response     NAT로 가능
VPC service → Private EC2             route와 SG 허용 시 가능

Security group은 stateful이므로 허용된 outbound connection의 response는 별도의 inbound response rule 없이 돌아온다. Network ACL은 stateless라 양방향 rule과 ephemeral port를 고려해야 한다.

애플리케이션과 DB를 계층별로 배치하기

일반적인 3-tier 구조는 다음과 같다.

flowchart LR
    U[Internet user]
    G[Internet Gateway]
    L[Public ALB]
    A[Private application]
    D[Isolated database]
    N[Outbound NAT]

    U --> G --> L --> A --> D
    A --> N --> G

각 계층의 route와 security group 책임을 나눈다.

계층 Public IP Internet inbound Internet outbound
Public ALB AWS가 관리 443 허용 응답 경로
Application 없음 ALB SG에서만 필요 시 NAT
Database 없음 App SG에서 DB port만 보통 없음

Security group reference를 사용하면 IP range보다 의도가 선명하다.

resource "aws_vpc_security_group_ingress_rule" "app_from_alb" {
  security_group_id            = aws_security_group.app.id
  referenced_security_group_id = aws_security_group.alb.id
  from_port                    = 8080
  to_port                      = 8080
  ip_protocol                  = "tcp"
}

DB가 patch나 backup을 위해 AWS service에 접근해야 한다면 무조건 NAT를 주기보다 service별 endpoint와 managed service 동작을 확인한다.

Availability Zone별 Subnet과 NAT 설계하기

Subnet은 하나의 Availability Zone에 속한다. 두 AZ를 쓴다면 계층별 subnet도 나눈다.

AZ-a: public-a, app-a, db-a
AZ-b: public-b, app-b, db-b

Zonal NAT Gateway를 하나만 AZ-a에 두고 app-b도 사용하면 두 문제가 생긴다.

AWS는 zonal NAT 사용 시 각 AZ에 NAT Gateway를 만들고 같은 AZ의 private subnet이 사용하도록 구성하는 것을 권장한다.

flowchart TD
    A1[App subnet AZ-a] --> N1[NAT AZ-a]
    A2[App subnet AZ-b] --> N2[NAT AZ-b]
    N1 --> I[Internet Gateway]
    N2 --> I

AWS에는 regional NAT Gateway 방식도 있으므로 사용 Region의 지원 여부, route model, 비용과 migration 조건을 공식 문서에서 확인한다. 기존 zonal 설계 예제와 혼합하지 않는다.

고가용성은 NAT 개수만의 문제가 아니다. ALB subnet, application replica, DB subnet group과 routing을 같은 AZ failure model로 점검한다.

NAT 비용과 VPC Endpoint 비교하기

NAT Gateway는 시간과 처리 data에 비용이 들고 cross-AZ 경로에는 추가 비용이 생길 수 있다. Private workload의 모든 AWS service traffic을 NAT로 보낼 필요는 없다.

목적지 가능한 경로
S3, DynamoDB Gateway VPC Endpoint 검토
PrivateLink 지원 서비스 Interface VPC Endpoint 검토
외부 SaaS/API NAT 또는 egress proxy
사내 network VPN, Direct Connect, Transit Gateway

VPC endpoint는 traffic을 AWS network 경로에 유지하고 NAT 의존을 줄일 수 있다. 하지만 interface endpoint는 AZ별 ENI와 시간 비용, security group, private DNS 설정이 있다.

비용만 보고 선택하지 않는다.

Container image pull도 registry API, image layer storage, log, secret service 등 여러 endpoint가 필요할 수 있다. 하나만 만들어 “인터넷 없이 된다”고 단정하지 않는다.

IPv6에서는 경로가 달라진다

IPv6 주소는 globally unique하며 IPv4 public address처럼 IGW에서 one-to-one NAT가 필요한 구조가 아니다. Public IPv6 inbound/outbound를 허용하려면 ::/0 IGW route와 security rule을 따로 본다.

IPv4 default: 0.0.0.0/0
IPv6 default: ::/0

Private IPv6 workload가 outbound만 시작하고 inbound 인터넷 연결을 받지 않게 하려면 egress-only Internet Gateway를 검토한다.

Private IPv6 subnet
::/0 → egress-only Internet Gateway

NAT Gateway의 NAT64와 Route 53 Resolver DNS64를 이용해 IPv6 workload가 IPv4 목적지에 접근하는 방식도 있다. Dual-stack에서는 다음을 각각 시험한다.

IPv4만 닫고 안심하지 않는다

0.0.0.0/0 ingress를 제거해도 ::/0이 열려 있으면 IPv6로 노출될 수 있다.

Security Group과 Network ACL 역할 나누기

Security group과 NACL은 route를 만들지 않는다. Route가 있어도 두 제어가 traffic을 거부할 수 있다.

특성 Security Group Network ACL
적용 단위 Network interface/resource Subnet
상태 Stateful Stateless
Rule Allow Allow와 deny
평가 전체 rule 조합 번호 순서 첫 match
다른 SG 참조 가능 불가

Resource 관계는 SG로 표현한다.

ALB SG → App SG : TCP 8080
App SG → DB SG  : TCP 5432
App SG → egress : 필요한 destination/port

NACL은 subnet guardrail이나 특정 CIDR deny 같은 defense-in-depth에 사용할 수 있다. Stateless이므로 client ephemeral port를 고려하지 않으면 SYN은 들어오지만 response가 막히는 장애가 난다.

기본 NACL을 무조건 복잡하게 만드는 것이 안전하다고 보기는 어렵다. 변경 주체와 검증 자동화가 없으면 장애 원인만 늘어난다.

Route 우선순위와 잘못된 Association 찾기

Route는 가장 구체적인 destination이 우선한다.

10.40.0.0/16 → local
10.40.8.0/24 → network appliance
0.0.0.0/0   → NAT

10.40.8.12는 default NAT가 아니라 더 구체적인 /24 경로를 따른다. Prefix list와 propagated route가 있으면 우선순위 규칙을 공식 문서로 확인한다.

Subnet은 명시적으로 custom route table과 associate하지 않으면 VPC main route table을 사용한다. Main table을 public으로 바꾼 뒤 새 subnet이 암묵적으로 연결되면 의도치 않게 IGW route를 가질 수 있다.

안전한 방향:

aws ec2 describe-route-tables \
  --filters Name=association.subnet-id,Values=subnet-example

ID는 가상 값이다. Output의 explicit association과 main table fallback을 함께 확인한다.

CIDR을 확장 가능하게 나누기

VPC CIDR은 on-premises, 다른 VPC, container/pod network와 겹치지 않게 정한다. 겹치면 peering, Transit Gateway, VPN routing이 어려워진다.

가상의 /16을 AZ와 계층으로 나눌 수 있다.

VPC 10.40.0.0/16

AZ-a public    10.40.0.0/24
AZ-a app       10.40.16.0/20
AZ-a database  10.40.32.0/24

AZ-b public    10.40.64.0/24
AZ-b app       10.40.80.0/20
AZ-b database  10.40.96.0/24

주소는 설명용이다. Application subnet은 autoscaling과 interface endpoint ENI 때문에 더 많은 IP가 필요할 수 있다. AWS가 subnet마다 예약하는 주소와 managed service의 IP 소비도 계산한다.

너무 작게 자르면 나중에 AZ를 추가하거나 blue/green environment를 띄울 공간이 부족하다. 너무 크게 잡으면 조직 전체 IPAM과 충돌한다.

Terraform으로 Route 관계 표현하기

다음은 핵심 관계만 보여 주는 가상 Terraform이다.

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
}

resource "aws_route_table" "public_a" {
  vpc_id = aws_vpc.main.id
}

resource "aws_route" "public_a_ipv4" {
  route_table_id         = aws_route_table.public_a.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.main.id
}

resource "aws_route_table_association" "public_a" {
  subnet_id      = aws_subnet.public_a.id
  route_table_id = aws_route_table.public_a.id
}

Private route는 NAT를 가리킨다.

resource "aws_route" "app_a_outbound" {
  route_table_id         = aws_route_table.app_a.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.a.id
}

resource "aws_route_table_association" "app_a" {
  subnet_id      = aws_subnet.app_a.id
  route_table_id = aws_route_table.app_a.id
}

NAT Gateway의 Elastic IP와 public subnet, IGW dependency도 실제 module에서 명확히 한다. depends_on을 남용하기보다 attribute reference로 dependency graph를 만든다.

Public IP 자동 할당을 의도대로 명시한다.

resource "aws_subnet" "app_a" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.40.16.0/20"
  availability_zone       = "ap-northeast-2a"
  map_public_ip_on_launch = false
}

AZ 이름은 account마다 mapping 차이를 고려하고 실제 module에서는 AZ ID 전략을 검토한다.

Flow Log와 Reachability로 장애 찾기

연결이 안 될 때 무조건 security group만 바꾸지 않는다. Packet 경로를 순서대로 확인한다.

1. DNS가 기대한 IP를 반환하는가
2. Source subnet route가 올바른 target을 고르는가
3. NAT/IGW/endpoint 상태가 정상인가
4. Source SG egress와 destination SG ingress가 허용되는가
5. 양쪽 NACL과 ephemeral port가 허용되는가
6. Application이 올바른 address/port에서 listen하는가
7. Return route가 존재하는가

VPC Flow Logs는 interface, subnet, VPC 수준의 accepted/rejected flow를 관측하는 데 도움이 된다.

version account-id interface-id srcaddr dstaddr srcport dstport protocol packets bytes start end action log-status

실제 log에는 IP와 account 정보가 있으므로 접근과 retention을 제한한다. Flow log가 application-level TLS나 HTTP 오류를 설명하지는 않는다.

Reachability Analyzer 같은 경로 분석 도구로 route, SG, NACL 구성을 확인할 수 있다. NAT port exhaustion, DNS, remote service 거부처럼 정적 경로 밖의 문제는 별도 지표가 필요하다.

인터넷 노출 여부를 자동으로 검증하기

Policy test는 tag가 아니라 effective path를 본다.

DB subnet:
  IGW default route 없음
  NAT default route 없음
  public IP 자동 할당 false

App subnet:
  IGW default route 없음
  같은 AZ NAT 또는 승인된 egress
  public IP 자동 할당 false

Public subnet:
  IGW route 있음
  public-facing resource만 허용

실패 시나리오:

Routing

노출과 protocol

구현 체크리스트

Route와 주소

가용성과 비용

보안과 관측

마무리

AWS VPC에서 public과 private은 subnet 생성 화면의 이름이 아니라 route table이 만드는 network path다. Internet Gateway로 가는 route가 있으면 public subnet이고, IPv4 resource가 직접 인터넷을 사용하려면 public address와 보안 허용도 필요하다.

Private application은 Internet Gateway로 직접 가지 않는다. 필요할 때 NAT를 통해 outbound connection을 만들고, DB처럼 인터넷이 필요 없는 계층은 isolated subnet에 둔다. AWS 서비스 traffic은 VPC endpoint로 더 좁힐 수 있다.

고가용성은 AZ별 subnet뿐 아니라 egress 경로에도 적용된다. Zonal NAT를 사용한다면 같은 AZ에 배치하고, 비용과 장애 요구에 따라 regional 방식과 endpoint를 검토한다. IPv6 route와 egress-only 경로도 IPv4와 별도로 설계한다.

마지막으로 route, security group, NACL은 서로 다른 역할이다. 연결 장애나 노출을 tag 하나로 판단하지 않고 source부터 destination과 return traffic까지 effective path를 검증해야 한다.

관련 노트

참고 자료